home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / GDebi / GDebi.py < prev    next >
Text File  |  2009-09-23  |  33KB  |  786 lines

  1. # Copyright (c) 2005-2009 Canonical Ltd
  2. #
  3. # AUTHOR:
  4. # Michael Vogt <mvo@ubuntu.com>
  5. #
  6. # This file is part of GDebi
  7. #
  8. # GDebi is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU General Public License as published
  10. # by the Free Software Foundation; either version 2 of the License, or (at
  11. # your option) any later version.
  12. #
  13. # GDebi is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  16. # General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with GDebi; if not, write to the Free Software
  20. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  21. #
  22.  
  23.  
  24. import sys
  25. import os
  26. import string
  27. import warnings
  28. from warnings import warn
  29. warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning)
  30. import apt
  31. import apt_pkg
  32.  
  33. import pygtk
  34. pygtk.require("2.0")
  35. import glib
  36. import gobject
  37. import gtk
  38. import pango
  39. import vte
  40. import urllib
  41. import fcntl
  42. import posix
  43. import time
  44. import thread
  45. import re
  46.  
  47. from DebPackage import DebPackage, Cache
  48. from SimpleGtkbuilderApp import SimpleGtkbuilderApp
  49. from apt.progress import InstallProgress
  50. from GDebiCommon import GDebiCommon, utf8
  51. from gettext import gettext as _
  52.  
  53. # the timeout when the termial is expanded if no activity from dpkg
  54. # is happening 
  55. GDEBI_TERMINAL_TIMEOUT=4*60.0
  56.  
  57. class GDebi(SimpleGtkbuilderApp, GDebiCommon):
  58.  
  59.     def __init__(self, datadir, options, file=""):
  60.         GDebiCommon.__init__(self,datadir,options,file)
  61.         localesApp="gdebi"
  62.         localesDir="/usr/share/locale"
  63.  
  64.         SimpleGtkbuilderApp.__init__(self, path=datadir+"/gdebi.ui")
  65.         # use a nicer default icon
  66.         icons = gtk.icon_theme_get_default()
  67.         try:
  68.           logo=icons.load_icon("gnome-mime-application-x-deb", 48, 0)
  69.           if logo != "":
  70.             gtk.window_set_default_icon_list(logo)
  71.         except Exception, e:
  72.           print "Error loading logo"
  73.           pass
  74.   
  75.         # setup status
  76.         self.context=self.statusbar_main.get_context_id("context_main_window")
  77.         self.statusbar_main.push(self.context,_("Loading..."))
  78.  
  79.         # setup drag'n'drop
  80.         self.window_main.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
  81.                                        gtk.DEST_DEFAULT_HIGHLIGHT |
  82.                                        gtk.DEST_DEFAULT_DROP,
  83.                                        [('text/uri-list',0,0)],
  84.                                        gtk.gdk.ACTION_COPY)
  85.  
  86.         self.window_main.set_sensitive(False)
  87.         self.notebook_details.set_sensitive(False)
  88.         self.hbox_main.set_sensitive(False)
  89.  
  90.         # show what we have
  91.         self.window_main.show()
  92.         #self.vte_terminal.set_font_from_string("monospace 10")
  93.         
  94.         self.cprogress = self.CacheProgressAdapter(self.progressbar_cache)
  95.         if not self.openCache():
  96.             self.show_alert(gtk.MESSAGE_ERROR, self.error_header, self.error_body)
  97.             sys.exit(1)
  98.         self.statusbar_main.push(self.context, "")
  99.         
  100.         # setup the details treeview
  101.         self.details_list = gtk.ListStore(gobject.TYPE_STRING)
  102.         column = gtk.TreeViewColumn("")
  103.         render = gtk.CellRendererText()
  104.         column.pack_start(render, True)
  105.         column.add_attribute(render, "markup", 0)
  106.         self.treeview_details.append_column(column)
  107.         self.treeview_details.set_model(self.details_list)
  108.  
  109.         # setup the files treeview
  110.         column = gtk.TreeViewColumn("")
  111.         render = gtk.CellRendererText()
  112.         column.pack_start(render, True)
  113.         column.add_attribute(render, "text", 0)
  114.         self.treeview_files.append_column(column)
  115.  
  116.         if file != "" and os.path.exists(file):
  117.             self.window_main.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
  118.             while gtk.events_pending(): gtk.main_iteration()        
  119.             self.open(file)
  120.             self.window_main.window.set_cursor(None)
  121.         
  122.         self.window_main.set_sensitive(True)
  123.  
  124.     def _get_file_path_from_dnd_dropped_uri(self, uri):
  125.         """ helper to get a useful path from a drop uri"""
  126.         path = urllib.url2pathname(uri) # escape special chars
  127.         path = path.strip('\r\n\x00') # remove \r\n and NULL
  128.         # get the path to file
  129.         if path.startswith('file:\\\\\\'): # windows
  130.             path = path[8:] # 8 is len('file:///')
  131.         elif path.startswith('file://'): # nautilus, rox
  132.             path = path[7:] # 7 is len('file://')
  133.         elif path.startswith('file:'): # xffm
  134.             path = path[5:] # 5 is len('file:')
  135.         return path
  136.     
  137.     def on_menuitem_quit_activate(self, widget):
  138.         try:
  139.             gtk.main_quit()
  140.         except:
  141.             # if we are outside of the main loop, just exit
  142.             sys.exit(0)
  143.  
  144.     def on_window_main_drag_data_received(self, widget, context, x, y,
  145.                                           selection, target_type, timestamp):
  146.         """ call when we got a drop event """
  147.         uri = selection.data.strip()
  148.         uri_splitted = uri.split() # we may have more than one file dropped
  149.         for uri in uri_splitted:
  150.             path = self._get_file_path_from_dnd_dropped_uri(uri)
  151.             #print 'path to open', path
  152.             if path.endswith(".deb"):
  153.                 self.window_main.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
  154.                 while gtk.events_pending(): gtk.main_iteration()        
  155.                 self.open(path)
  156.                 self.window_main.window.set_cursor(None)
  157.  
  158.     def open(self, file):
  159.         res = GDebiCommon.open(self, file)
  160.         if res == False:
  161.             self.show_alert(gtk.MESSAGE_ERROR, self.error_header, self.error_body)
  162.             return False
  163.             
  164.         self.statusbar_main.push(self.context, "")
  165.  
  166.         # set window title
  167.         self.window_main.set_title(_("Package Installer - %s") % 
  168.                                    self._deb.pkgName)
  169.  
  170.         # set name and ungrey some widgets
  171.         self.label_name.set_markup(self._deb.pkgName)
  172.         self.notebook_details.set_sensitive(True)
  173.         self.hbox_main.set_sensitive(True)
  174.  
  175.         # set description
  176.         buf = self.textview_description.get_buffer()
  177.         try:
  178.             long_desc = ""
  179.             raw_desc = string.split(utf8(self._deb["Description"]), "\n")
  180.             # append a newline to the summary in the first line
  181.             summary = raw_desc[0]
  182.             raw_desc[0] = ""
  183.             long_desc = "%s\n" % summary
  184.             for line in raw_desc:
  185.                 tmp = string.strip(line)
  186.                 if tmp == ".":
  187.                     long_desc += "\n"
  188.                 else:
  189.                     long_desc += tmp + "\n"
  190.             #print long_desc
  191.             # do some regular expression magic on the description
  192.             # Add a newline before each bullet
  193.             p = re.compile(r'^(\s|\t)*(\*|0|-)',re.MULTILINE)
  194.             long_desc = p.sub('\n*', long_desc)
  195.             # replace all newlines by spaces
  196.             p = re.compile(r'\n', re.MULTILINE)
  197.             long_desc = p.sub(" ", long_desc)
  198.             # replace all multiple spaces by
  199.             # newlines
  200.             p = re.compile(r'\s\s+', re.MULTILINE)
  201.             long_desc = p.sub("\n", long_desc)
  202.             # write the descr string to the buffer
  203.             buf.set_text(long_desc)
  204.             # tag the first line with a bold font
  205.             tag = buf.create_tag(None, weight=pango.WEIGHT_BOLD)
  206.             iter = buf.get_iter_at_offset(0)
  207.             (start, end) = iter.forward_search("\n",
  208.                                                gtk.TEXT_SEARCH_TEXT_ONLY,
  209.                                                None)
  210.             buf.apply_tag(tag , iter, end)
  211.         except KeyError:
  212.             buf.set_text("No description is available")
  213.  
  214.         # set various status bits
  215.         self.label_version.set_text(self._deb["Version"])
  216.         self.label_maintainer.set_text(utf8(self._deb["Maintainer"]))
  217.         self.label_priority.set_text(self._deb["Priority"])
  218.         self.label_section.set_text(utf8(self._deb["Section"]))
  219.         self.label_size.set_text(self._deb["Installed-Size"] + " KB")
  220.  
  221.         # set file list
  222.         store = gtk.TreeStore(str)
  223.         try:
  224.             header = store.append(None, [_("Package control data")])
  225.             for name in self._deb.control_filelist:
  226.                 store.append(header, [name])
  227.             header = store.append(None, [_("Upstream data")])
  228.             for name in self._deb.filelist:
  229.                 store.append(header, [name])
  230.         except Exception, e:
  231.             print "Exception while reading the filelist: '%s'" % e
  232.             store.clear()
  233.             store.append(None, [_("Error reading filelist")])
  234.         self.treeview_files.set_model(store)
  235.         self.treeview_files.expand_all()
  236.         # and the file content textview
  237.         font_desc = pango.FontDescription('monospace')
  238.         self.textview_file_content.modify_font(font_desc)
  239.  
  240.         # check the deps
  241.         if not self._deb.checkDeb():
  242.             self.label_status.set_markup("<span foreground=\"red\" weight=\"bold\">"+
  243.                                          _("Error: ") +
  244.                                          glib.markup_escape_text(self._deb._failureString) +
  245.                                          "</span>")
  246.         self.button_install.set_label(_("_Install Package"))
  247.  
  248.             self.button_install.set_sensitive(False)
  249.             self.button_details.hide()
  250.             return
  251.         
  252.  
  253.         # set version_info_{msg,title} strings
  254.         self.compareDebWithCache()
  255.         self.getChanges()
  256.  
  257.         if self._deb.compareToVersionInCache() == DebPackage.VERSION_SAME:
  258.             self.label_status.set_text(_("Same version is already installed"))
  259.             self.button_install.set_label(_("_Reinstall Package"))
  260.             self.button_install.grab_default()
  261.             self.button_install.set_sensitive(True)
  262.             self.button_details.hide()
  263.             return
  264.  
  265.         if self.version_info_title != "" and self.version_info_msg != "":
  266.             msg = "<big><b>%s</b></big>\n\n%s" % (self.version_info_title,
  267.               self.version_info_msg)
  268.             dialog = gtk.MessageDialog(parent=self.window_main,
  269.                                        flags=gtk.DIALOG_MODAL,
  270.                                        type=gtk.MESSAGE_INFO,
  271.                                        buttons=gtk.BUTTONS_CLOSE)
  272.             dialog.set_markup(msg)
  273.             dialog.run()
  274.             dialog.destroy()
  275.  
  276.         # load changes into (self.install, self.remove, self.unauthenticated)
  277.         if len(self.remove) == len(self.install) == 0:
  278.             self.button_details.hide()
  279.         else:
  280.             self.button_details.show()
  281.             
  282.         self.label_status.set_markup(self.deps)
  283.         #img = gtk.Image()
  284.         #img.set_from_stock(gtk.STOCK_APPLY,gtk.ICON_SIZE_BUTTON)
  285.         #self.button_install.set_image(img)
  286.         self.button_install.set_label(_("_Install Package"))
  287.         self.button_install.set_sensitive(True)
  288.         self.button_install.grab_default()
  289.  
  290.     def on_treeview_files_cursor_changed(self, treeview):
  291.         " the selection in the files list chanaged "
  292.         model = treeview.get_model()
  293.         (path, col) = treeview.get_cursor()
  294.         name = model[path][0]
  295.         # if we are at the top-level, do nothing
  296.         if len(path) < 2:
  297.             return
  298.         # parent path == 0 means we look at the control information
  299.         # parent path == 1 means we look at the data
  300.         parent_path = path[0]
  301.         if name.endswith("/"):
  302.             data = _("Selection is a directory")
  303.         elif parent_path == 0:
  304.             try:
  305.                 data = self._deb.control_content(name)
  306.             except Exception, e:
  307.                 data = _("Error reading file content '%s'") % e
  308.         elif parent_path == 1:
  309.             try:
  310.                 data = self._deb.data_content(name)
  311.             except Exception, e:
  312.                 data = _("Error reading file content '%s'") % e
  313.         else:
  314.             assert False, "NOT REACHED"
  315.         if not data:
  316.             data = _("File content can not be extracted")
  317.         buf = self.textview_file_content.get_buffer()
  318.         buf.set_text(data)
  319.  
  320.     def on_button_details_clicked(self, widget):
  321.         #print "on_button_details_clicked"
  322.         # sanity check
  323.         if not self._deb:
  324.           return
  325.         self.details_list.clear()
  326.         for rm in self.remove:
  327.             self.details_list.append([_("<b>To be removed: %s</b>") % rm])
  328.         for inst in self.install:
  329.             self.details_list.append([_("To be installed: %s") % inst])
  330.         self.dialog_details.set_transient_for(self.window_main)
  331.         self.dialog_details.run()
  332.         self.dialog_details.hide()
  333.  
  334.     def on_open_activate(self, widget):
  335.         #print "open"
  336.         # build dialog
  337.         self.window_main.set_sensitive(False)
  338.         fs = gtk.FileChooserDialog(parent=self.window_main,
  339.                                    buttons=(gtk.STOCK_CANCEL, 
  340.                                             gtk.RESPONSE_CANCEL, 
  341.                                             gtk.STOCK_OPEN, 
  342.                                             gtk.RESPONSE_OK),
  343.                                    action=gtk.FILE_CHOOSER_ACTION_OPEN,
  344.                                    title=_("Open Software Package"))
  345.         fs.set_default_response(gtk.RESPONSE_OK)
  346.         # set filter
  347.         filter = gtk.FileFilter()
  348.         filter.add_pattern("*.deb")
  349.         filter.set_name(_("Software packages"))
  350.         #fs.add_filter(filter)
  351.         fs.set_filter(filter)
  352.         # run it!
  353.         if fs.run() == gtk.RESPONSE_OK:
  354.             #print fs.get_filename()
  355.             self.open(fs.get_filename())
  356.         fs.destroy()
  357.         self.window_main.set_sensitive(True)
  358.  
  359.     def on_refresh_activate(self, widget):
  360.         #print "refresh"
  361.         self.window_main.set_sensitive(False)
  362.         self.openCache()
  363.         if self._deb:
  364.             self.open(self._deb.file)
  365.         self.window_main.set_sensitive(True)
  366.  
  367.     def on_about_activate(self, widget):
  368.         #print "about"
  369.         from Version import VERSION
  370.         self.dialog_about.set_version(VERSION)
  371.         self.dialog_about.run()
  372.         self.dialog_about.hide()
  373.  
  374.     def on_button_install_clicked(self, widget):
  375.         self.install_completed=False
  376.         # check if we actually have a deb, see #213725
  377.         if not self._deb:
  378.             err_header = _("File not found")
  379.             err_body = _("You tried to install a file that does not "
  380.                          "(or no longer) exist. ")
  381.             dia = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR,
  382.                                     gtk.BUTTONS_OK, "")
  383.             dia.set_markup("<b><big>%s</big></b>" % err_header)
  384.             dia.format_secondary_text(err_body)
  385.             dia.run()
  386.             dia.destroy()
  387.             return
  388.         # do it
  389.         self.statusbar_main.push(self.context,_("Installing package file..."))
  390.         if widget != None and len(self.unauthenticated) > 0:
  391.             primary = _("Install unauthenticated software?")
  392.             secondary = _("Malicious software can damage your data "
  393.                           "and take control of your system.\n\n"
  394.                           "The packages below are not authenticated and "
  395.                           "could therefor be of malicious nature.")
  396.             msg = "<big><b>%s</b></big>\n\n%s" % (primary, secondary)
  397.             dialog = gtk.MessageDialog(parent=self.dialog_deb_install,
  398.                                        flags=gtk.DIALOG_MODAL,
  399.                                        type=gtk.MESSAGE_WARNING,
  400.                                        buttons=gtk.BUTTONS_YES_NO)
  401.             dialog.set_markup(msg)
  402.             dialog.set_border_width(6)
  403.             scrolled = gtk.ScrolledWindow()
  404.             textview = gtk.TextView()
  405.             textview.set_cursor_visible(False)
  406.             textview.set_editable(False) 
  407.             buf = textview.get_buffer()
  408.             buf.set_text("\n".join(self.unauthenticated))
  409.             scrolled.add(textview)
  410.             scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
  411.             scrolled.show()
  412.             dialog.vbox.pack_start(scrolled)
  413.             textview.show()
  414.             res = dialog.run()
  415.             dialog.destroy()
  416.             if res != gtk.RESPONSE_YES:
  417.                 return
  418.  
  419.         msg_hdr = _("You need to grant administrative rights to install software")
  420.         msg_bdy = _("""
  421. It is a possible security risk to install packages files manually.
  422. Install software from trustworthy software distributors only.
  423. """)
  424.         if os.getuid() != 0:
  425.             os.execl("/usr/bin/gksu", "gksu", "--desktop",
  426.                      "/usr/share/applications/gdebi.desktop",
  427.                      "--message","<big><b>%s</b></big>\n\n%s" % (msg_hdr,msg_bdy),
  428.                      "--always-ask-pass",
  429.                      "--", "gdebi-gtk", "--non-interactive",
  430.                      self._deb.file)
  431.  
  432.         if not self.try_acquire_lock():
  433.             self.statusbar_main.push(self.context,
  434.                                      _("Failed to install package file"))
  435.             self.show_alert(gtk.MESSAGE_ERROR, self.error_header, self.error_body)
  436.             return False
  437.             
  438.         # lock for install
  439.         self.window_main.set_sensitive(False)
  440.         self.button_deb_install_close.set_sensitive(False)
  441.         # clear terminal
  442.         #self.vte_terminal.feed(str(0x1b)+"[2J")
  443.         self.dialog_deb_install.set_transient_for(self.window_main)
  444.         self.dialog_deb_install.show_all()
  445.  
  446.         if len(self.install) > 0 or len(self.remove) > 0:
  447.             # FIXME: use the new python-apt acquire interface here,
  448.             # or rather use it in the apt module and raise exception
  449.             # when stuff goes wrong!
  450.             if not self.acquire_lock():
  451.               self.show_alert(gtk.MESSAGE_ERROR, self.error_header, self.error_body)
  452.               return False
  453.             fprogress = self.FetchProgressAdapter(self.progressbar_install,
  454.                                                 self.label_action,
  455.                                                 self.dialog_deb_install)
  456.             iprogress = self.InstallProgressAdapter(self.progressbar_install,
  457.                                                     self.vte_terminal,
  458.                                                     self.label_action,
  459.                                                     self.expander_install)
  460.             errMsg = None
  461.             try:
  462.                 res = self._cache.commit(fprogress,iprogress)
  463.             except IOError, msg:
  464.                 res = False
  465.                 errMsg = "%s" % msg
  466.                 header = _("Could not download all required files")
  467.                 body = _("Please check your internet connection or "
  468.                         "installation medium.")
  469.             except SystemError, msg:
  470.                 res = False
  471.                 header = _("Could not install all dependencies"),
  472.                 body = _("Usually this is related to an error of the "
  473.                         "software distributor. See the terminal window for "
  474.                         "more details.")
  475.             if not res:
  476.                 self.show_alert(gtk.MESSAGE_ERROR, header, body, msg,
  477.                                 parent=self.dialog_deb_install)
  478.                     
  479.                 self.label_install_status.set_markup("<span foreground=\"red\" weight=\"bold\">%s</span>" % header)
  480.                 self.button_deb_install_close.set_sensitive(True)
  481.                 self.button_deb_install_close.grab_default()
  482.                 self.statusbar_main.push(self.context,_("Failed to install package file"))
  483.                 return 
  484.     
  485.         # install the package itself
  486.         self.label_action.set_markup("<b><big>" +
  487.                                      _("Installing %s") % self._deb.pkgName+
  488.                                      "</big></b>")
  489.         dprogress = self.DpkgInstallProgress(self._deb.file,
  490.                                              self.label_install_status,
  491.                                              self.progressbar_install,
  492.                                              self.vte_terminal,
  493.                                              self.expander_install)
  494.         dprogress.commit()
  495.         self.install_completed=True
  496.         #self.label_action.set_markup("<b><big>"+_("Package installed")+"</big></b>")
  497.         # show the button
  498.         self.button_deb_install_close.set_sensitive(True)
  499.         self.button_deb_install_close.grab_default()
  500.         #Close if checkbox is selected
  501.         if self.checkbutton_autoclose.get_active():
  502.             self.on_button_deb_install_close_clicked(None)
  503.         self.label_action.set_markup("<b><big>"+_("Installation finished")+"</big></b>")
  504.         if dprogress.exitstatus == 0:
  505.             self.label_install_status.set_markup("<i>"+_("Package '%s' was installed") % os.path.basename(self._deb.file)+"</i>")
  506.         else:
  507.             self.label_install_status.set_markup("<b>"+_("Failed to install package '%s'") % os.path.basename(self._deb.file)+"</b>")
  508.             self.expander_install.set_expanded(True)
  509.         self.statusbar_main.push(self.context,_("Installation complete"))
  510.         # FIXME: Doesn't stop notifying
  511.         #self.window_main.set_property("urgency-hint", 1)
  512.  
  513.         # reopen the cache, reread the file
  514.         self.openCache()
  515.         if self._cache._depcache.BrokenCount > 0:
  516.             err_header = _("Failed to completely install all dependencies")
  517.             err_body = _("To fix this run 'sudo apt-get install -f' in a "
  518.                          "terminal window.")
  519.             self.show_alert(gtk.MESSAGE_ERROR, err_header, err_body)
  520.         self.open(self._deb.file)
  521.         
  522.     def on_button_deb_install_close_clicked(self, widget):
  523.         # FIXME: doesn't turn it off
  524.         #self.window_main.set_property("urgency-hint", 0)
  525.         self.dialog_deb_install.hide()
  526.         self.window_main.set_sensitive(True)
  527.     
  528.     def on_checkbutton_autoclose_clicked(self, widget):
  529.         if self.install_completed:
  530.             self.on_button_deb_install_close_clicked(None)            
  531.  
  532.     def on_window_main_delete_event(self, *args):
  533.         if self.window_main.get_property("sensitive"):
  534.             if gtk.main_level() > 0:
  535.                 gtk.main_quit()
  536.             return False
  537.         else: 
  538.             return True
  539.  
  540.     def show_alert(self, type, header, body=None, details=None, parent=None):
  541.         if parent is not None:
  542.              self.dialog_hig.set_transient_for(parent)
  543.         else:
  544.              self.dialog_hig.set_transient_for(self.window_main)
  545.  
  546.         message = "<b><big>%s</big></b>" % header
  547.         if not body == None:
  548.              message = "%s\n\n%s" % (message, body)
  549.         self.label_hig.set_markup(message)
  550.   
  551.         if not details == None:
  552.              buffer = self.textview_hig.get_buffer()
  553.              buffer.set_text(str(details))
  554.              self.expander_hig.set_expanded(False)
  555.              self.expander_hig.show()
  556.              
  557.         if type == gtk.MESSAGE_ERROR:
  558.              self.image_hig.set_property("stock", "gtk-dialog-error")
  559.         elif type == gtk.MESSAGE_WARNING:
  560.              self.image_hig.set_property("stock", "gtk-dialog-warning")
  561.         elif type == gtk.MESSAGE_INFO:
  562.              self.image_hig.set_property("stock", "gtk-dialog-info")
  563.              
  564.         res = self.dialog_hig.run()
  565.         self.dialog_hig.hide()
  566.         if res == gtk.RESPONSE_CLOSE:
  567.             return True
  568.         return False
  569.         
  570.     # embedded classes
  571.     class DpkgInstallProgress(object):
  572.         def __init__(self, debfile, status, progress, term, expander):
  573.             self.debfile = debfile
  574.             self.status = status
  575.             self.progress = progress
  576.             self.term = term
  577.             self.term_expander = expander
  578.             self.time_last_update = time.time()
  579.             self.term_expander.set_expanded(False)
  580.         def commit(self):
  581.             def finish_dpkg(term, pid, status, lock):
  582.                 " helper "
  583.                 self.exitstatus = posix.WEXITSTATUS(status)
  584.                 #print "dpkg finished %s %s" % (pid,status)
  585.                 #print "exit status: %s" % self.exitstatus
  586.                 #print "was signaled %s" % posix.WIFSIGNALED(status)
  587.                 lock.release()
  588.  
  589.             # get a lock
  590.             lock = thread.allocate_lock()
  591.             lock.acquire()
  592.  
  593.             # ui
  594.             self.status.set_markup("<i>"+_("Installing '%s'...") % \
  595.                                    os.path.basename(self.debfile)+"</i>")
  596.             self.progress.pulse()
  597.             self.progress.set_text("")
  598.  
  599.             # prepare reading the pipe
  600.             (readfd, writefd) = os.pipe()
  601.             fcntl.fcntl(readfd, fcntl.F_SETFL,os.O_NONBLOCK)
  602.             #print "fds (%i,%i)" % (readfd,writefd)
  603.  
  604.             # the command
  605.             cmd = "/usr/bin/dpkg"
  606.             argv = [cmd,"--status-fd", "%s"%writefd, "-i", self.debfile]
  607.             env = ["VTE_PTY_KEEP_FD=%s"% writefd,
  608.                    "DEBIAN_FRONTEND=gnome",
  609.                    "APT_LISTCHANGES_FRONTEND=gtk"]
  610.             #print cmd
  611.             #print argv
  612.             #print env
  613.             #print self.term
  614.  
  615.             # prepare for the fork
  616.             reaper = vte.reaper_get()
  617.             signal_id = reaper.connect("child-exited", finish_dpkg, lock)
  618.             pid = self.term.fork_command(command=cmd, argv=argv, envv=env)
  619.             read = ""
  620.             while lock.locked():
  621.                 while True:
  622.                     try:
  623.                         read += os.read(readfd,1)
  624.                     except OSError, (errno,errstr):
  625.                         # resource temporarly unavailable is ignored
  626.                         if errno != 11:
  627.                             print errstr
  628.                         break
  629.                     self.time_last_update = time.time()
  630.                     if read.endswith("\n"):
  631.                         statusl = string.split(read, ":")
  632.                         if len(statusl) < 3:
  633.                             print "got garbage from dpkg: '%s'" % read
  634.                             read = ""
  635.                             break
  636.                         status = statusl[2].strip()
  637.                         #print status
  638.                         if status == "error" or status == "conffile-prompt":
  639.                             self.term_expander.set_expanded(True)
  640.                         read = ""
  641.                 self.progress.pulse()
  642.                 while gtk.events_pending():
  643.                     gtk.main_iteration()
  644.                 time.sleep(0.2)
  645.                 # if the terminal has not reacted for some time, do something
  646.                 if (not self.term_expander.get_expanded() and 
  647.                     (self.time_last_update + GDEBI_TERMINAL_TIMEOUT) < time.time()):
  648.                   self.term_expander.set_expanded(True)
  649.             self.progress.set_fraction(1.0)
  650.             reaper.disconnect(signal_id)
  651.     
  652.     class InstallProgressAdapter(InstallProgress):
  653.         def __init__(self,progress,term,label,term_expander):
  654.             InstallProgress.__init__(self)
  655.             self.progress = progress
  656.             self.term = term
  657.             self.term_expander = term_expander
  658.             self.finished = False
  659.             self.action = label
  660.             self.time_last_update = time.time()
  661.             reaper = vte.reaper_get()
  662.             reaper.connect("child-exited",self.child_exited)
  663.             self.env = ["VTE_PTY_KEEP_FD=%s"% self.writefd,
  664.                         "DEBIAN_FRONTEND=gnome",
  665.                         "APT_LISTCHANGES_FRONTEND=gtk"]
  666.         def child_exited(self,term, pid, status):
  667.             #print "child_exited: %s %s %s %s" % (self,term,pid,status)
  668.             self.apt_status = status
  669.             self.finished = True
  670.         def error(self, pkg, errormsg):
  671.             # FIXME: display a msg
  672.             self.term_expander.set_expanded(True)
  673.         def conffile(self, current, new):
  674.             # FIXME: display a msg or expand term
  675.             self.term_expander.set_expanded(True)
  676.         def startUpdate(self):
  677.             #print "startUpdate"
  678.             apt_pkg.PkgSystemUnLock()
  679.             self.action.set_markup("<i>"+_("Installing dependencies...")+"</i>")
  680.             self.progress.set_fraction(0.0)
  681.             self.progress.set_text("")
  682.         def statusChange(self, pkg, percent, status):
  683.             self.progress.set_fraction(percent/100.0)
  684.             self.progress.set_text(status)
  685.             self.time_last_update = time.time()
  686.         def updateInterface(self):
  687.             InstallProgress.updateInterface(self)
  688.             while gtk.events_pending():
  689.                 gtk.main_iteration()
  690.             if (not self.term_expander.get_expanded() and 
  691.                 (self.time_last_update + GDEBI_TERMINAL_TIMEOUT) < time.time()):
  692.               self.term_expander.set_expanded(True)
  693.             # sleep just long enough to not create a busy loop
  694.             time.sleep(0.01)
  695.         def fork(self):
  696.             pid = self.term.forkpty(self.env)
  697.             if pid == 0:
  698.                 # *grumpf* workaround bug in vte here (gnome bug #588871)
  699.                 for env in self.env:
  700.                     (key, value) = env.split("=")
  701.                     os.environ[key] = value
  702.             return pid
  703.         def waitChild(self):
  704.             while not self.finished:
  705.                 self.updateInterface()
  706.             return self.apt_status
  707.         
  708.     class FetchProgressAdapter(apt.progress.FetchProgress):
  709.         def __init__(self,progress,action,main):
  710.             #print "FetchProgressAdapter.__init__()"
  711.             self.progress = progress
  712.             self.action = action
  713.             self.main = main
  714.         def start(self):
  715.             #print "start()"
  716.             self.action.set_markup("<i>"+_("Downloading additional package files...")+"</i>")
  717.             self.progress.set_fraction(0)
  718.         def stop(self):
  719.             #print "stop()"
  720.             pass
  721.         def pulse(self):
  722.             at_item = min(self.currentItems + 1, self.totalItems)
  723.             if self.currentCPS > 0:
  724.                 self.progress.set_text(_("File %s of %s at %sB/s") % (at_item,self.totalItems,apt_pkg.SizeToStr(self.currentCPS)))
  725.             else:
  726.                 self.progress.set_text(_("File %s of %s") % (at_item,self.totalItems))
  727.             self.progress.set_fraction(self.currentBytes/self.totalBytes)
  728.             while gtk.events_pending():
  729.                 gtk.main_iteration()
  730.             return True
  731.         def mediaChange(self, medium, drive):
  732.             #print "mediaChange %s %s" % (medium, drive)
  733.             msg = _("Please insert '%s' into the drive '%s'" % (medium,drive))
  734.             dialog = gtk.MessageDialog(parent=self.main,
  735.                                        flags=gtk.DIALOG_MODAL,
  736.                                        type=gtk.MESSAGE_QUESTION,
  737.                                        buttons=gtk.BUTTONS_OK_CANCEL)
  738.             dialog.set_markup(msg)
  739.             res = dialog.run()
  740.             #print res
  741.             dialog.destroy()
  742.             if  res == gtk.RESPONSE_OK:
  743.                 return True
  744.             return False
  745.  
  746.     class CacheProgressAdapter(apt.progress.FetchProgress):
  747.         def __init__(self, progressbar):
  748.             self.progressbar = progressbar
  749.         def update(self, percent):
  750.             self.progressbar.show()
  751.             self.progressbar.set_fraction(percent/100.0)
  752.             #self.progressbar.set_text(self.op)
  753.             while gtk.events_pending():
  754.                 gtk.main_iteration()
  755.         def done(self):
  756.             self.progressbar.hide()
  757.         
  758. if __name__ == "__main__":
  759.     app = GDebi("data/",None)
  760.  
  761.     pkgs = ["cw"]
  762.     for pkg in pkgs:
  763.         print "installing %s" % pkg
  764.         app._cache[pkg].markInstall()
  765.  
  766.     for pkg in app._cache:
  767.         if pkg.markedInstall or pkg.markedUpgrade:
  768.             print pkg.name
  769.  
  770.     apt_pkg.PkgSystemLock()
  771.     app.dialog_deb_install.set_transient_for(app.window_main)
  772.     app.dialog_deb_install.show_all()
  773.  
  774.     # install the dependecnies
  775.     fprogress = app.FetchProgressAdapter(app.progressbar_install,
  776.                                          app.label_action,
  777.                                          app.dialog_deb_install)
  778.     iprogress = app.InstallProgressAdapter(app.progressbar_install, 
  779.                                            app.vte_terminal,
  780.                                            app.label_action,
  781.                                            app.expander_install)
  782.     res = app._cache.commit(fprogress,iprogress)
  783.     print "commit retured: %s" % res
  784.     
  785.     gtk.main()
  786.